using System;
using System.Collections.Generic;
using System.Text;

using System.IO;
using System.Xml;
using System.Diagnostics;
using System.Reflection;
using System.Threading;

namespace WMOSSExperts.PackageBuilder
{
    public class PackageBuilder : IDisposable
    {

        internal const String WSSSchemaURI = "http://schemas.microsoft.com/sharepoint/";

        #region "Private members"
        private List<ProjectConfiguration> _sharepointProjects;
        private String _workingFolder;
        private SortedList<String, BaseSolutionItem> _solutionItems = new SortedList<string,BaseSolutionItem>();
        #endregion

        #region "Public members"
        //Folders excluded from DDF file
        public List<string> ExcludedFolder = new List<string>();

        //Extension files excluded from the ddf file
        public List<String> ExcludedExtension = new List<string>();

        //files excluded from the ddf file
        public List<String> ExcludedFiles = new List<string>();

        /// <summary>
        /// Gets or sets the package name
        /// </summary>
        public String PackageName;

        /// <summary>
        /// Gets or sets the WSS solution ID
        /// </summary>
        public Guid SolutionId;

        /// <summary>
        /// Gets or sets value to specify that the Web server be reset through Microsoft Internet Information Services (IIS).
        /// </summary>
        public Boolean RestartIIS;

        //TODO Implement the deployment server type property on PackageBuilder class
        //public String DeploymentServerType;
        #endregion

        #region "Manage build environment"
        /// <summary>
        /// Inits the package builder before building the WSS Solution
        /// </summary>
        private void Init()
        {
            //Creates the build environment
            this.CreateEnvironnement();

            //Add exlcusion
            this.ExcludedExtension.Add(".cs");
            this.ExcludedFolder.Add(".svn");
            this.ExcludedFiles.Add("Microsoft.SharePoint.Search.xml");
            this.ExcludedFiles.Add("Microsoft.SharePoint.Search.dll");

        }

        /// <summary>
        /// Creates the build environment
        /// </summary>
        private void CreateEnvironnement()
        {
            this.CreateWorkingDirectory();
        }

        /// <summary>
        /// Create the build directory
        /// </summary>
        private void CreateWorkingDirectory()
        {

            Console.WriteLine(Path.GetTempPath());
            _workingFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),"WMOSSExperts.PackageBuilder");
            if (!Directory.Exists(_workingFolder)) Directory.CreateDirectory(_workingFolder);

            _workingFolder = Path.Combine(_workingFolder, this.PackageName);
            if (Directory.Exists(_workingFolder)) this.DeleteWorkingDirectory();
            Directory.CreateDirectory(_workingFolder);

        }

        /// <summary>
        /// Delete the build directory
        /// </summary>
        private void DeleteWorkingDirectory()
        {
            if (Directory.Exists(_workingFolder))
            {
                try
                {
                    Directory.Delete(_workingFolder, true);
                }
                catch
                {
                    //TODO Find process which doesn't free its resource
                }
            }
        }

        /// <summary>
        /// Dispose and clean the build environment
        /// </summary>
        public void Dispose()
        {
            //TODO Activate the clean when package copy will be implemented
            //this.DeleteWorkingDirectory();
        }

        #endregion


        #region "Build"


        /// <summary>
        /// Add a project configuration to be built
        /// 
        /// </summary>
        public void AddProject(ProjectConfiguration project)
        {
            
            if (_sharepointProjects == null) _sharepointProjects = new List<ProjectConfiguration>();

            _sharepointProjects.Add(project);
            
        }

        /// <summary>
        /// Build the wss solution
        /// </summary>
        public void Build()
        {
            //Checks required data
            if (this.SolutionId == Guid.Empty)
                throw new ArgumentNullException("SolutionId", "This property is required to build a SharePoint solution");

            if (String.IsNullOrEmpty(this.PackageName))
                throw new ArgumentNullException("PackageName", "This property is required to build a SHarePoint Solution");

            //Inits the build environment
            this.Init();

            //BrowseSolutionItems solution items
            this.BrowseProjects();

            //Create the Wss solution
            this.CreateOutputPackage();
        }



       

        /// <summary>
        /// Launch the copy of solution item for each visual studio projects
        /// </summary>
        private void BrowseProjects()
        {
            foreach (ProjectConfiguration project in _sharepointProjects)
            {
                //Load all content items
                String sourceFolderPath = Path.Combine(project.FullPath, "12");
                if (Directory.Exists(sourceFolderPath))
                {
                    DirectoryInfo sourceFolder = new DirectoryInfo(sourceFolderPath);
                    this.BrowseSolutionItems(sourceFolder, Path.Combine(_workingFolder,"12"), ManifestElementType.RootFile);
                    
                }

                //Load assemblies
                String outputPath = project.OutputPath;
                //TODO verify how a relative path is handled if it begins with ..\
                if (!Path.IsPathRooted(outputPath)) outputPath = Path.Combine(project.FullPath, outputPath);
                if (Directory.Exists(outputPath))
                {
                    DirectoryInfo sourceFolder = new DirectoryInfo(outputPath);
                    this.BrowseAssemblies(sourceFolder, Path.Combine(_workingFolder, "Assemblies"), project.OutputFileName, false);
                }
                
            }

        }

        private void BrowseAssemblies(DirectoryInfo sourceFolder, string targetFolderPath, string outputFileName, bool embedAllBinaries)
        {
            //create the target directory
            //if (!Directory.Exists(targetFolderPath)) Directory.CreateDirectory(targetFolderPath);

            //BrowseSolutionItems files
            String pattern = "*.dll";
            if (!embedAllBinaries) pattern = outputFileName;

            FileInfo[] files = sourceFolder.GetFiles(pattern);
            foreach (FileInfo file in files)
            {
                /*String targetFilePath = Path.Combine(targetFolderPath, file.Name);
                file.CopyTo(targetFilePath);

                FileInfo targetFile = new FileInfo(targetFilePath);*/
                BaseSolutionItem item = BaseSolutionItem.GetSolutionItem(ManifestElementType.Assembly, file);

                //Check if the item must be excluded
                this.IsExcludedItem(item);

                //Add this item in the list of solution items
                if (this.GetItemInSolution(file.FullName) == null)
                    _solutionItems.Add(file.FullName.ToLower(), item);
            }
        }

        /// <summary>
        /// Browse Solution Items (files and sub directories)
        /// </summary>
        /// <param name="sourceFolder"></param>
        /// <param name="targetFolderPath"></param>
        private void BrowseSolutionItems(DirectoryInfo sourceFolder, string targetFolderPath, ManifestElementType elementType)
        {
            //BrowseSolutionItems files
            FileInfo[] files = sourceFolder.GetFiles();
            foreach (FileInfo file in files)
            {

                /*String targetFilePath = Path.Combine(targetFolderPath, file.Name);
                file.CopyTo(targetFilePath);

                FileInfo targetFile = new FileInfo(targetFilePath);*/
                BaseSolutionItem item = BaseSolutionItem.GetSolutionItem(elementType, file);

                //Check if the item must be excluded
                this.IsExcludedItem(item);

                //Add this item in the list of solution items
                if (this.GetItemInSolution(file.FullName) == null)
                    _solutionItems.Add(file.FullName.ToLower(), item);

                //Specific rules for Feature
                if (item is FeatureItem && (item as FeatureItem).FileName.ToLower() == "feature.xml")
                {
                    FeatureItem featureItem = (FeatureItem)item;
                    featureItem.IsManifest = true;

                    XmlDocument featureFile = new XmlDocument();
                    featureFile.Load(featureItem.SourcePath);

                    // Read the XmlDocument (Directory Node)
                    XmlNodeList elementManifestList = featureFile.GetElementsByTagName("ElementManifest");
                    for (int i = 0; i < elementManifestList.Count; i++)
                    {
                        FileInfo manifestFile = new FileInfo(Path.Combine(sourceFolder.FullName, elementManifestList[0].Attributes["Location"].Value));
                        FeatureItem manifestItem = (FeatureItem)this.GetItemInSolution(manifestFile.FullName);
                        if (manifestItem == null) 
                        {
                            manifestItem = (FeatureItem)BaseSolutionItem.GetSolutionItem(ManifestElementType.Feature, manifestFile);
                            _solutionItems.Add(manifestFile.FullName.ToLower(), manifestItem);
                        }

                        manifestItem.IsManifestElement = true;

                     }

                }

            }

            //BrowseSolutionItems directories
            DirectoryInfo[] folders = sourceFolder.GetDirectories();
            foreach (DirectoryInfo folder in folders)
            {
                //Verify that the folder is not in the exclude list
                if (ExcludedFolder.Contains(folder.Name.ToLower())) continue;

                String subTargetFolderPath = Path.Combine(targetFolderPath, folder.Name);
                //if (!Directory.Exists(subTargetFolderPath)) Directory.CreateDirectory(subTargetFolderPath);

                ManifestElementType childElement = elementType;

                switch (folder.Name.ToLower())
                {
                    case "features":
                        if (elementType == ManifestElementType.TemplateFile)
                        {
                            childElement = ManifestElementType.Feature;
                        }
                        break;
                    case "template":
                        if (elementType == ManifestElementType.RootFile)
                        {
                            childElement = ManifestElementType.TemplateFile;
                        }
                        break;
                }

                this.BrowseSolutionItems(folder, subTargetFolderPath, childElement);
            }
        }

        private BaseSolutionItem GetItemInSolution(String fullPath)
        {
            BaseSolutionItem item = null;
            _solutionItems.TryGetValue(fullPath.ToLower(), out item);
            return item;
        }

        private void IsExcludedItem(BaseSolutionItem item)
        {
            if (ExcludedExtension.Contains(Path.GetExtension(item.FileName).ToLower()) |
                ExcludedFiles.Contains(item.FileName.ToLower()))
                item.IsExcluded = true;
        }

        #endregion




        #region "Create Output Package"

        private void CreateOutputPackage()
        {
            StringBuilder cabFile = this.GetCabHeader();
            XmlDocument manifest = this.InitManifest();

            //browse all solution elements to create cab file and manifest.xml
            foreach (BaseSolutionItem item in _solutionItems.Values)
            {
                if (!item.IsExcluded)
                {
                    cabFile.AppendLine(item.BuildDDFLine());
                    item.AddToManifest(manifest);
                }
            }


            //Finalize the ddf file and writes it on the disk; write the manifest on the disk
            cabFile.AppendLine("\".\\manifest.xml\" \"manifest.xml\"");
            File.WriteAllText(Path.Combine(_workingFolder, this.PackageName + ".ddf"), cabFile.ToString());
            manifest.Save(Path.Combine(_workingFolder, "manifest.xml"));


            //Create the cab file according to the ddf file previously created
            this.MakeCab();
        }

        /// <summary>
        /// Create the Cab.ddf file header used to build wsp file for solution packaging
        /// </summary>
        private StringBuilder GetCabHeader()
        {

            StringBuilder cabFile = new StringBuilder();
            cabFile.AppendLine(";");
            cabFile.AppendLine(".Set CabinetNameTemplate=" + this.PackageName + ".wsp");
            cabFile.AppendLine(".set DiskDirectoryTemplate=CDROM ; All cabinets go in a single directory");
            cabFile.AppendLine(".Set CompressionType=MSZIP;** All files are compressed in cabinet files");
            cabFile.AppendLine(".Set UniqueFiles='ON'");
            cabFile.AppendLine(".Set Cabinet=on");
            cabFile.AppendLine(".Set DiskDirectory1=.");

            return cabFile;

        }

        /// <summary>
        /// Init the manifest xml file with solution data (SolutionId, ResetWebServer and deploymentTargetServer)
        /// </summary>
        /// <returns></returns>
        private XmlDocument InitManifest()
        {
            XmlDocument doc = new XmlDocument();
            XmlNode solutionNode = doc.CreateNode(XmlNodeType.Element, "Solution", WSSSchemaURI);
            //XmlNode solutionNode = doc.CreateNode(XmlNodeType.Element, "Solution", null);
            doc.AppendChild(solutionNode);
            
            XmlAttribute solutionIdAttribute = doc.CreateAttribute("SolutionId");
            solutionIdAttribute.Value = this.SolutionId.ToString();
            solutionNode.Attributes.Append(solutionIdAttribute);

            XmlAttribute resetIISAttribute = doc.CreateAttribute("ResetWebServer");
            resetIISAttribute.Value = this.RestartIIS.ToString().ToUpper();
            solutionNode.Attributes.Append(resetIISAttribute);

            //TODO add the deploymentTagServer attribute

            return doc;

        }

        /// <summary>
        /// Run MakeCab.exe process and move files to PACKAGE directory
        /// </summary>
        /// <param name="targetDir"></param>
        /// <param name="_packageName"></param>
        private void MakeCab()
        {
            //Run Cab creation
            System.Environment.CurrentDirectory = _workingFolder;
            System.Diagnostics.Process makecab = new System.Diagnostics.Process();
            makecab.StartInfo = new ProcessStartInfo("makecab.exe", "/F " + this.PackageName + ".ddf");
            bool Result = makecab.Start();

            int i = 0;
            while (!makecab.HasExited) //Exited event does not seems to work -> Use a loop to wait for makeCab process finish
            {
                System.Threading.Thread.Sleep(500);
                i++;

                if (i == 60) //Wait 30s max
                    break;
            }

            //Move files
            /*if (!Directory.Exists(Path.Combine(ProjectDir, "PACKAGE")))
                Directory.CreateDirectory(Path.Combine(ProjectDir, "PACKAGE"));

            if (File.Exists(Path.Combine(ProjectDir, "manifest.xml")))
            {
                if (File.Exists(Path.Combine(ProjectDir, "PACKAGE\\manifest.xml")))
                    File.Delete(Path.Combine(ProjectDir, "PACKAGE\\manifest.xml"));

                File.Move(Path.Combine(ProjectDir, "manifest.xml"), Path.Combine(ProjectDir, "PACKAGE\\manifest.xml"));
            }
            if (File.Exists(Path.Combine(ProjectDir, "cab.ddf")))
            {
                if (File.Exists(Path.Combine(ProjectDir, "PACKAGE\\cab.ddf")))
                    File.Delete(Path.Combine(ProjectDir, "PACKAGE\\cab.ddf"));

                File.Move(Path.Combine(ProjectDir, "cab.ddf"), Path.Combine(ProjectDir, "PACKAGE\\cab.ddf"));
            }
            if (File.Exists(Path.Combine(ProjectDir, WspName + ".wsp")))
            {
                if (File.Exists(Path.Combine(ProjectDir, "PACKAGE\\" + WspName + ".wsp")))
                    File.Delete(Path.Combine(ProjectDir, "PACKAGE\\" + WspName + ".wsp"));

                File.Move(Path.Combine(ProjectDir, WspName + ".wsp"), Path.Combine(ProjectDir, "PACKAGE\\" + WspName + ".wsp"));
            }*/
        }

        #endregion


       
 
    }
}
